package com.panopset.net.ftp;

import static com.panopset.Util.*;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.StringTokenizer;
import java.util.Vector;

public class Server {

    private final String host;
    private final String user;
    private final String pwd;
    private String remotePath;
    private FTPCommandSocket commandSocket;
    private FTPDataSocket dataSocket;

    public Server(String host, String remotePath, String user, String pwd)
            throws IOException {
        if (!isPopulated(user)) {
            this.user = "anonymous";
            this.pwd = "anonymous";
        } else {
            this.user = user;
            this.pwd = pwd;
        }
        this.remotePath = remotePath;
        this.host = host;
    }

    public void close() throws IOException {
        getCommandSocket().close();
        commandSocket = null;
        dataSocket = null;
    }

    public void connect() throws IOException {
        dspmsg(sendCommand("USER " + user));

        dspmsg(sendCommand("PASS " + pwd));
        readLine();
        bin();
        StringTokenizer st = new StringTokenizer(remotePath, "/");
        while (st.hasMoreTokens()) {
            cwd(st.nextToken());
        }
        readLine();
    }

    public String cwd(String dir) throws IOException {
        String response = sendCommand("CWD " + dir);
        if (response.startsWith("550 ")) {
            if (response.toLowerCase().indexOf("not a directory") > -1) {
                throw new IOException(response);
            }
            dspmsg(sendCommand("MKD " + dir));
            dspmsg(response = sendCommand("CWD " + dir));
            sendVerify("CWD " + dir, "220 ", "200 ", "250 ");
        }
        return response;
    }

    /**
     * Enter binary mode for sending binary files.
     */
    private void bin() throws IOException {
        sendVerify("TYPE I", "200 ");
    }

    public String sendCommand(String command) throws IOException {
        return getCommandSocket().getCommandResponse(command);
    }

    public String sendVerify(String command, String expected)
            throws IOException {
        return getCommandSocket().sendVerify(command, expected);
    }

    public String sendVerify(String command, String... expected)
            throws IOException {
        return getCommandSocket().sendVerify(command, expected);
    }

    private FTPCommandSocket getCommandSocket() throws IOException {
        if (commandSocket == null) {
            commandSocket = new FTPCommandSocket(host);
            connect();
        }
        return commandSocket;
    }

    private String readLine() throws IOException {
        return getCommandSocket().readLine();
    }

    public void getFile(File toFile) throws IOException {
        FTPDataSocket ds = getNewDataSocket();
        String name = toFile.getName();

        String response = sendCommand("RETR " + name);

        if (response.startsWith("550")) {
            dspmsg("Aborting, File not on server.");
            return;
        }

        BufferedInputStream inp = ds.getInputStream();
        response = readLine();
        File parent = toFile.getParentFile();
        if (parent != null && !parent.exists()) {
            mkdirs(parent);
        }
        createNewFile(toFile);
        FileOutputStream fos = new FileOutputStream(toFile);
        BufferedOutputStream output = new BufferedOutputStream(fos);
        byte[] buffer = new byte[ds.getSocket()
                .getReceiveBufferSize()];
        long totalBytesRead = 0;
        int bytesRead = inp.read(buffer);
        while (bytesRead != -1) {
            output.write(buffer, 0, bytesRead);
            totalBytesRead += bytesRead;
            bytesRead = inp.read(buffer);
        }
        output.flush();
        output.close();
        ds.close();
        dspmsg(toFile.getName() + " bytes:" + totalBytesRead);
        response = readLine();
        if (!response.startsWith("226 ")) {
            throw new IOException("Unexpected response: " + response);
        }
    }

    public synchronized Vector<String> list() throws IOException {
        FTPDataSocket ds = getNewDataSocket();
        Vector<String> rtn = new Vector<String>();
        String response = null;

        BufferedInputStream inp = ds.getInputStream();

        sendCommand("LIST");

        response = readLine();
        System.out.println(response);
        byte[] buffer = new byte[ds.getSocket()
                .getReceiveBufferSize()];
        long totalBytesRead = 0;
        int bytesRead = inp.read(buffer);
        StringBuffer sb = new StringBuffer();
        while (bytesRead != -1) {
            int i = 0;
            while (i < bytesRead) {
                if (buffer[i] == 13) {
                    rtn.add(sb.toString());
                    sb = new StringBuffer();
                } else if (buffer[i] == 10) {
                    // nothing to do
                } else {
                    sb.append((char) buffer[i]);
                }
                i++;
            }
            totalBytesRead += bytesRead;
            bytesRead = inp.read(buffer);
        }
        if (sb.length() > -1) {
            rtn.add(sb.toString());
        }

        readLine();
        ds.close();
        return rtn;
    }

    public boolean stor(InputStream inputStream, String filename)
            throws IOException {
        String response = null;

        FTPDataSocket ds = getNewDataSocket();

        sendCommand("STOR "  + filename);

        BufferedOutputStream outputStream = ds.getOutputStream();

        readLine();

        byte[] buffer = new byte[ds.getSocket().getSendBufferSize()];
        int bytesRead = 0;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
        outputStream.flush();
        outputStream.close();
        inputStream.close();

        response = readLine();
        return response.startsWith("226 ");
    }

    static class DataPortInfo {
        private Integer port;
        private String ip;
    }

    private DataPortInfo dataPortInfo;

    private DataPortInfo getDataPort() throws IOException {
        if (dataPortInfo == null) {
            dataPortInfo = new DataPortInfo();
            String response = sendCommand("PASV");
            if (response == null) {
                return null;
            }
            int opening = response.indexOf('(');
            int closing = response.indexOf(')', opening + 1);
            if (closing > 0) {
                String dataLink = response.substring(opening + 1, closing);
                StringTokenizer tokenizer = new StringTokenizer(dataLink, ",");
                try {
                    dataPortInfo.ip = tokenizer.nextToken() + "." + tokenizer.nextToken() + "."
                            + tokenizer.nextToken() + "." + tokenizer.nextToken();
                    dataPortInfo.port = Integer.parseInt(tokenizer.nextToken()) * 256
                            + Integer.parseInt(tokenizer.nextToken());
                } catch (Exception e) {
                    log(e);
                }
            } else {
                throw new IOException("Unexpected response: " + response);
            }
        }
        return dataPortInfo;
    }

    /**
     * Send the PASV command to port 21 to get the data socket port.
     *
     * @return DataSocket
     * @throws IOException
     */
    private FTPDataSocket getNewDataSocket() throws IOException {
        if (dataSocket == null) {

        } else {
            dataSocket.close();
        }
        dataPortInfo = null;
        dataSocket = new FTPDataSocket(getDataPort().ip, getDataPort().port);
        return dataSocket;
    }

    @Override
    public String toString() {
        return "commandSocket: " + commandSocket + "\n dataSocket: "
                + dataSocket;
    }

}
